﻿#region Copyright 2011 Mike Goatly
// This source is subject to the the MIT License (MIT)
// All rights reserved.
#endregion

namespace Lifti.Locking
{
    using System;
    using System.Threading;

    /// <summary>
    /// The implementation of a lock manager for a full text index. This implements a multiple reader, single writer
    /// type of locking behaviour.
    /// </summary>
#if !NETFX_CORE
    [Serializable]
#endif
    public class LockManager : ILockManager
    {
        #region Private Variables
        /// <summary>
        /// The reader/writer lock instance for this lock manager. No recursion is allowed
        /// </summary>
#if !NETFX_CORE
        [NonSerialized]
#endif
        private ReaderWriterLockSlim readerWriterLock;
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="LockManager"/> class.
        /// </summary>
        public LockManager()
            : this(LockRecursionPolicy.NoRecursion)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LockManager"/> class.
        /// </summary>
        /// <param name="lockRecursionPolicy">The lock recursion policy.</param>
        public LockManager(LockRecursionPolicy lockRecursionPolicy)
        {
            this.Enabled = true;
            this.readerWriterLock = new ReaderWriterLockSlim(lockRecursionPolicy);
        }
        #endregion

        #region Events
        /// <summary>
        /// Occurs when a read lock has been acquired.
        /// </summary>
        public event EventHandler ReadLockAcquired;

        /// <summary>
        /// Occurs when a read lock has been released.
        /// </summary>
        public event EventHandler ReadLockReleased;

        /// <summary>
        /// Occurs when a write lock has been acquired.
        /// </summary>
        public event EventHandler WriteLockAcquired;

        /// <summary>
        /// Occurs when a write lock has been released.
        /// </summary>
        public event EventHandler WriteLockReleased;
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets or sets a value indicating whether locking is enabled. This defaults to <c>true</c> - setting 
        /// it to <c>false</c> will result in slightly increased performance, but should be done only 
        /// it can be guaranteed that requests to the index will always come from the same thread.
        /// </summary>
        /// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value>
        public bool Enabled
        {
            get;
            set;
        } 
        #endregion

        #region Public Methods
        /// <summary>
        /// Obtains a read lock. This will remain active until the provided lock is disposed. Multiple threads
        /// are able to obtain read locks simultaneously.
        /// </summary>
        /// <returns>
        /// An instance of <see cref="ILock"/> that represents the read lock.
        /// </returns>
        public ILock AcquireReadLock()
        {
            if (this.Enabled)
            {
                // Enter the lock
                this.readerWriterLock.EnterReadLock();

                this.OnReadLockAcquired();

                // Return a read lock instance
                return new ReadLock(this);
            }

            // Locking is not enabled, so return a new disposable null lock that does nothing
            return new NullLock();
        }

        /// <summary>
        /// Obtains a write lock. This will remain active until the provided lock is disposed. Only one write lock
        /// can be obtained at any one time. Any outstanding read locks will be processed before the write lock is
        /// granted, and any read locks subsequently requested will be queued until the write lock is released.
        /// </summary>
        /// <returns>
        /// An instance of <see cref="ILock"/> that represents the write lock.
        /// </returns>
        public ILock AcquireWriteLock()
        {
            if (this.Enabled)
            {
                // Enter the lock
                this.readerWriterLock.EnterWriteLock();

                this.OnWriteLockAcquired();

                // Return a new instance of a write lock
                return new WriteLock(this);
            }

            // Locking is not enabled, so return a new disposable null lock that does nothing
            return new NullLock();
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion

        #region Internal Methods
        /// <summary>
        /// Releases a read lock.
        /// </summary>
        internal void ReleaseReadLock()
        {
            this.OnReadLockReleased();
            this.readerWriterLock.ExitReadLock();
        }

        /// <summary>
        /// Releases a write lock.
        /// </summary>
        internal void ReleaseWriteLock()
        {
            this.OnWriteLockReleased();
            this.readerWriterLock.ExitWriteLock();
        }
        #endregion

        #region Protected Methods
        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.readerWriterLock != null)
                {
                    this.readerWriterLock.Dispose();
                    this.readerWriterLock = null;
                }
            }
        } 

        /// <summary>
        /// Called when a read lock has been acquired.
        /// </summary>
        protected virtual void OnReadLockAcquired()
        {
            this.RaiseEventIfSubscribed(this.ReadLockAcquired);
        }

        /// <summary>
        /// Called when a read lock has been released.
        /// </summary>
        protected virtual void OnReadLockReleased()
        {
            this.RaiseEventIfSubscribed(this.ReadLockReleased);
        }

        /// <summary>
        /// Called when a write lock has been acquired.
        /// </summary>
        protected virtual void OnWriteLockAcquired()
        {
            this.RaiseEventIfSubscribed(this.WriteLockAcquired);
        }

        /// <summary>
        /// Called when a write lock has been released.
        /// </summary>
        protected virtual void OnWriteLockReleased()
        {
            this.RaiseEventIfSubscribed(this.WriteLockReleased);
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Raises the event, but only if anyone has subscribed to it.
        /// </summary>
        /// <param name="handler">The event handler to call.</param>
        private void RaiseEventIfSubscribed(EventHandler handler)
        {
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }
        #endregion
    }
}
